﻿using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Silverlight;
using Microsoft.Xna.Framework;
using System.Collections.Generic;

namespace SStuff.XnaInterop
{
    public partial class InteropModel
    {
        public IndexBuffer[] SL5IndexBuffers { get; set; }
        public VertexBuffer[] SL5VertexBuffers { get; set; }
        public Texture2D[] SL5Textures { get; set; }
        public Dictionary<string,InteropBone> IndexedBones { get; private set; }
        private bool _isPrepared;
        public bool IsPrepared { get { return _isPrepared; } }
        public void Prepare(GraphicsDevice device)
        {
            SL5IndexBuffers =
                IndexBuffers.Select(
                    ib => CreateIndexBuffer(device, ib)).ToArray();
            SL5VertexBuffers = VertexBuffers.Select(vb => CreateVertexBuffer(device, vb))
                                                                     .ToArray();
            SL5Textures = Textures.Select(tex => LoadBitmapAndMip(tex)).ToArray();

            IndexedBones = Bones.ToDictionary(b => b.Name);

            foreach (var interopMesh in Meshes)
            {
                interopMesh.Prepare(device, this);
            }
            _isPrepared = true;
        }

        private Texture2D CreateTexture(GraphicsDevice device, InteropTexture2D source)
        {
            var ms = new MemoryStream(source.PngData);
            var img = new BitmapImage();
            img.SetSource(ms);
            var result = new Texture2D(device, source.Width, source.Height, false, SurfaceFormat.Color);
            img.CopyTo(result);
            return result;
        }

        private Texture2D LoadBitmapAndMip(InteropTexture2D source)
        {
            // Load bitmap
            BitmapImage bs = new BitmapImage();
            bs.SetSource(new MemoryStream(source.PngData));

            // Get dimensions        
            int w = bs.PixelWidth;
            int h = bs.PixelHeight;

            if ((w % 2 != 0 && w != 1) || (h % 2 != 0 && h != 1))
                throw new InvalidOperationException("Bitmap must be power of 2.");

            // Calculate mip levels
            int mipLevels = 1;
            int maxDimension = Math.Max(w, h);
            while (maxDimension > 1)
            {
                mipLevels++;
                maxDimension /= 2;
            }

            // Create the chain
            Texture2D tex = new Texture2D(GraphicsDeviceManager.Current.GraphicsDevice, w, h, true, SurfaceFormat.Color);

            // Put bitmap into a renderable image
            Image image = new Image();
            image.Source = bs;

            // Generate mip level data
            for (int level = 0; level < mipLevels; level++)
            {
                var wb = new WriteableBitmap(w, h);

                // Scale to current mip level
                System.Windows.Media.ScaleTransform t = new System.Windows.Media.ScaleTransform();
                t.ScaleX = (double)w / (double)bs.PixelWidth;
                t.ScaleY = (double)h / (double)bs.PixelHeight;

                // Black out the image
                for (int c = 0; c < w * h; c++)
                    wb.Pixels[c] = 0;

                // Small mip levels are rendering as white, so don't render
                if (w > 1 && h > 1)
                    wb.Render(image, t);

                // Update WB
                wb.Invalidate();

                // Grab pixel data
                wb.CopyTo(tex, level, null, 0, 0);

                // Shrink for the next level
                if (w != 1)
                    w /= 2;
                if (h != 1)
                    h /= 2;
            }

            return tex;
        }

        private IndexBuffer CreateIndexBuffer(GraphicsDevice device, InteropIndexBuffer source)
        {
            var buffer = new IndexBuffer(device,
                                         source.Is32BitFormat
                                             ? IndexElementSize.ThirtyTwoBits
                                             : IndexElementSize.SixteenBits, source.IndexCount, BufferUsage.None);

            if (source.Is32BitFormat)
            {
                buffer.SetData(0, source.DataInt32, 0, source.IndexCount);
            }
            else
            {
                buffer.SetData(0, source.DataInt16, 0, source.IndexCount);
            }

            return buffer;
        }

        private VertexBuffer CreateVertexBuffer(GraphicsDevice device, InteropVertexBuffer source)
        {
            var buffer = new VertexBuffer(device, BuildVertexDeclaration(source), source.VertexCount, BufferUsage.None);
            buffer.SetData(0, source.Data, 0, source.Data.Length, 1);


            return buffer;
        }

        private VertexDeclaration BuildVertexDeclaration(InteropVertexBuffer vb)
        {
            var decl =
                new VertexDeclaration(
                    vb.Elements.Select(
                        el =>
                        new VertexElement(el.Offset,
                                          (VertexElementFormat)
                                          Enum.Parse(typeof(VertexElementFormat), el.Format, true),
                                          (VertexElementUsage)Enum.Parse(typeof(VertexElementUsage), el.Usage, true),
                                          el.UsageIndex)).ToArray());
            return decl;
        }

        public void CopyAbsoluteBonesTo(Matrix[] _absoluteBones)
        {
            // we assume bones are sorted
            for (int i = 0; i < Bones.Length; i++)
            {
                if (Bones[i].IsRoot)
                {
                    _absoluteBones[i] = Bones[i].Transform;
                }
                else
                {
                    _absoluteBones[i] = Bones[i].Transform * _absoluteBones[Bones[i].ParentIndex];
                }
            }
        }

       
    }



    public partial class InteropMesh
    {
        public void Prepare(GraphicsDevice device, InteropModel model)
        {
            foreach (var interopMeshPart in Parts)
            {
                interopMeshPart.Prepare(device, model);
            }
        }
    }

    public partial class InteropMeshPart
    {
        public Dictionary<string,InteropTextureUsage> IndexedTextureUsages { get; private set; }
        public VertexBuffer SL5VertexBuffer { get; set; }
        public IndexBuffer SL5IndexBuffer { get; set; }
        public void Prepare(GraphicsDevice device, InteropModel model)
        {
            IndexedTextureUsages = TextureUsages.ToDictionary(u => u.EffectParameterName);
            foreach (var usage in TextureUsages)
                usage.Prepare(device, model);

            SL5VertexBuffer = model.SL5VertexBuffers[VertexBufferIndex];
            SL5IndexBuffer = model.SL5IndexBuffers[IndexBufferIndex];
        }

        public void Draw(GraphicsDevice gd)
        {
            gd.SetVertexBuffer(SL5VertexBuffer);
            gd.Indices = SL5IndexBuffer;
            gd.DrawIndexedPrimitives(PrimitiveType.TriangleList, VertexOffset, 0, NumVertices, StartIndex, PrimitiveCount);
        }
    }

    public partial class InteropTextureUsage
    {
        public Texture2D SL5Texture { get; set; }
        public void Prepare(GraphicsDevice device, InteropModel model)
        {
            if(TextureIndex != -1)
                SL5Texture = model.SL5Textures[TextureIndex];
        }
    }

}
